import { client, queryClient } from '@fill/client';
import type { ExportPayload, Config as FillConfig, Meeting as FillMeeting, Record as FillRecord, ImportPayload, Opinion, OpinionProgrammeBook } from '@fill/client';
import dayjs from 'dayjs';
import { basename, join } from '@tauri-apps/api/path';
import { copyFile, createDir, removeDir, removeFile, renameFile, writeFile } from '@tauri-apps/api/fs';
import keyBy from 'lodash.keyby';
import { invoke } from '@tauri-apps/api';
import type { Dictionary } from 'lodash';
import { flatten, groupBy, sortBy } from 'lodash';
import MD5 from 'md5';
import JSONBig from 'json-bigint';
import { v4 as uuidv4 } from 'uuid';
import localCnnfig from '../config';
import { EXPORT_FILE, FILL_NAME, FILL_TABLE_COLLECTION, LANHAI_SYSTEM, NODE_KEY, NODE_TYPE_ENUM, STATUS_VALUE, TABLE_COLLECTION, UPLOAD_BASE } from '../constants/default';
import { CONFIG_KEYS } from '../constants/config';
import { dataPath, filesPath } from '../util';
import type { IFillText } from '../components/app/Fill';
import { Book } from './modules/book';
import { Meeting } from './modules/meeting';
import { Programme } from './modules/programme';
import { Rectification } from './modules/rectification';
import { Report } from './modules/report';
import type { ILanhaiDataSchema, ILanhaiSpDef, ILanhaiSpNode, IPipe, IRecordSchema, IRecordSchemas, LanhaiDataSchema, ValueOf } from './modules/interface';
import type { Pipe } from './modules/pipe';
import { Opinion as OpinionModule } from './modules/opinion';
import { Record } from './modules/record';
import { Config } from './modules/config';
import type { w_sp_def, w_sp_node } from './types/sp_def';
import type { LanhaiFile } from './types/file';

interface IPipes {
  [key: string]: IPipe & Pipe
}

export type FillOpinion = Opinion & {
  programme_book: OpinionProgrammeBook | null
};

export interface FillData {
  meeting: FillMeeting[]
  opinion: FillOpinion[]
  config: {
    [key: string]: Omit<FillConfig, 'value'> & { value: any | any[] }
  }
  record: FillRecord[]
}

export interface IModules {
  [key: string]: IRecordSchema
}

export class Handle {
  pipes: IPipes = {
    opinion: new OpinionModule(),
    meeting: new Meeting(),
    programme: new Programme(),
    book: new Book(),
    report: new Report(),
    rectification: new Rectification(),
    record: new Record(),
    config: new Config(),
  };

  results!: IRecordSchemas;
  // 完整文件名
  file!: string;
  // 临时目录
  tmp!: string;
  root!: string;
  // 加密文件
  data!: string;
  modules: IModules;
  config!: Dictionary<FillConfig>;
  type = 0;
  fileName = '';
  other: {
    dataSource: any
    Readme: any
  } = {
      dataSource: undefined,
      Readme: undefined,
    };

  constructor() {
    this.modules = {

    };
  }

  async import(data: ILanhaiDataSchema, basePath: string, configStore: IFillText['config']) {
    console.log(data);
    // 前置处理数据
    this.type = 1;
    /**
     * 过滤掉 w_sp_node 中非当前节点数据
     */
    // 业务导入时 sp_node 必定存在 仅做特殊情况判断
    // sp_node 做 keyBy处理
    const sp_node: ILanhaiSpNode = {};
    if (data.has(TABLE_COLLECTION.SP_NODE)) {
      const _sp_node = data.get(TABLE_COLLECTION.SP_NODE) as LanhaiDataSchema;

      // 过滤处在当前节点的数据
      const sp_node_data = (_sp_node.data as w_sp_node[]).filter(item => item.F_SFDQJD_ === '1');
      // 以模块分组
      const sp_node_group: {
        [key: string]: w_sp_node[]
      } = groupBy(sp_node_data as w_sp_node[], 'F_TYPE_');
      console.log(sp_node_group);
      // 对分组内列表 keyBY -> F_REL_ID_
      Object.keys(sp_node_group).forEach((key) => {
        sp_node[key] = keyBy(sp_node_group[key] as w_sp_node[], 'F_REL_ID_');
      });
    }

    /**
     * w_sp_def 做 keyBy 处理
     */
    let sp_def: ILanhaiSpDef = {};
    if (data.has(CONFIG_KEYS.SP_DEF)) {
      const _sp_def = data.get(CONFIG_KEYS.SP_DEF) as LanhaiDataSchema;

      // 以模块分组
      sp_def = groupBy(_sp_def.data as w_sp_def[], 'F_TYPE_');
      // 对分组内列表升序排序
      Object.keys(sp_def).forEach((key) => {
        sp_def[key] = sortBy(sp_def[key] as w_sp_def[], 'F_SORT_');
      });
    }

    const results = await Promise.all(Object.values(this.pipes).map(async (pipe) => {
      return await pipe.setImportPath(basePath).import(data, sp_def, sp_node, configStore);
    }));
    const modules = results.filter(item => !!item.length);
    const flattenModules = flatten(modules);
    if (!flattenModules.length)
      return flattenModules;

    const schemas: ImportPayload = {
      opinion: [],
      programme: [],
      book: [],
      meeting: [],
      config: [],
      record: [],
      action: {
        name: '',
        module: '',
        types: this.type.toString(),
      },
    };

    const module_texts: string[] = [];
    flattenModules.forEach((item) => {
      const key = item.module as string as keyof ImportPayload;
      module_texts.push(...item.text);
      Object.keys(schemas).includes(item.module) && (schemas[key] = item.data as any);
    });
    schemas.action!.module = module_texts.join() as string;
    // schemas.programme = [];
    // schemas.opinion = [];
    // schemas.meeting = [];
    // schemas.config = [];
    // schemas.record = [];
    // schemas.book = [];
    // schemas.action = {
    //   name: '',
    //   module: '',
    //   types: this.type,
    // };
    console.log(schemas, results);

    await queryClient.fetchQuery({
      queryKey: ['handle.import'],
      queryFn: () => client.mutation(['handle.import', { arg: schemas }]),
    });

    await queryClient.refetchQueries({
      stale: true,
    });

    return this;
  }

  // 处理导出数据
  async export(module: string) {
    // 读库
    let [
      meeting,
      opinion,
      config,
      record,
    ] = await queryClient.fetchQuery({
      queryKey: ['all.get'],
      queryFn: () => client.query(['all.get', { arg: null }]),
    });

    // 过滤不符合要求的数据
    // Rust 后端关联查询未过滤 status 由渲染部分过滤
    opinion && (opinion = opinion.filter(item => item.programme_book?.status === STATUS_VALUE.PROCESSED || item.programme_book?.book_status === STATUS_VALUE.PROCESSED));

    config.length && (config = config.map((item) => {
      let value = item.value;

      try {
        value = JSONBig.parse(item.value);
      }
      catch (error) {
        console.log(error);
      }

      return {
        ...item,
        value,
      };
    }));

    const _config = keyBy(config, 'key');
    // Rust 后端关联查询未过滤 status 由渲染部分过滤
    [CONFIG_KEYS.REPORT, CONFIG_KEYS.RECTIFICATION].forEach((item) => {
      (_config[item]?.value as any)?.status !== STATUS_VALUE.PROCESSED && delete _config[item];
    });
    this.config = _config;

    const data = {
      meeting,
      opinion,
      config: _config,
      record,
    };

    // 需要导出的数据集合
    let results: IRecordSchemas = {};

    /**
     * w_sp_def 做 keyBy 处理
     */
    let sp_def: ILanhaiSpDef = {};
    if (_config[CONFIG_KEYS.SP_DEF]) {
      const _sp_def = _config[CONFIG_KEYS.SP_DEF].value as unknown as w_sp_def[];

      // 以模块分组
      sp_def = groupBy(_sp_def, 'F_TYPE_');
      // 对分组内列表升序排序
      Object.keys(sp_def).forEach((key) => {
        sp_def[key] = sortBy(sp_def[key] as w_sp_def[], 'F_SORT_');
      });
    }

    // 需要处理的模块
    const pipes = module === 'all' ? Object.keys(this.pipes) : ['config', 'record', module];

    // 执行所需模块逻辑
    const _results = (await Promise.all(
      pipes.map(async key => await this.pipes[key].setData(data).export(sp_def)),
    )).filter(item => !!Object.keys(item).length);

    // 合并数据
    results = _results.reduce((prev, current) => {
      const data: IRecordSchemas = {
        ...current,
        ...prev,
      };

      // 节点数据
      data.w_sp_node = Object.assign(data.w_sp_node, {
        data: data.w_sp_node.data.concat(current.w_sp_node.data),
      });
      // 历史数据
      data.w_sp_history = Object.assign(data.w_sp_history, {
        data: data.w_sp_history.data.concat(current.w_sp_history.data),
      });
      // 资源数据
      data.sys_file = Object.assign(data.sys_file, {
        data: data.sys_file.data.concat(current.sys_file.data),
      });

      return data;
    });

    // 处理数据源
    const data_source = _config[CONFIG_KEYS.DATA_SOURCE].value;

    // 处理 Readme
    const readme = _config[CONFIG_KEYS.README].value;

    // key 对应文件名 不做严格规范简化流程
    this.other = {
      dataSource: data_source,
      Readme: readme,
    };

    console.log(results);

    this.results = results;

    return this;
  }

  // 加密压缩
  async generate(encrypt = false) {
    const {
      path: {
        tmp,
        data,
      },
    } = await localCnnfig();
    // 生成临时目录相关数据
    const root = `${UPLOAD_BASE}${dayjs().format('YYYYMMDDHHmmss')}`;
    this.fileName = root;
    const name = uuidv4().replaceAll('-', '');
    this.file = await join(data, `${name}.chrtc`);
    this.tmp = await join(tmp, name);
    this.root = await join(this.tmp, root);
    this.data = await join(data, `离线填报-${name}.chrtc`);

    // 创建目录
    await createDir(await join(this.root, ...FILL_NAME), {
      recursive: true,
    });
    await createDir(await join(this.root, ...LANHAI_SYSTEM));
    const files_path = await join(this.root, ...EXPORT_FILE);
    await createDir(files_path);

    // 写入加密
    await Promise.all(Object.values(this.results).map(async (item) => {
      // 创建目录
      const base = await join(this.root, ...item.path);
      console.log(base);
      // 写入
      const table_file = await join(base, item.table);
      console.log(table_file);

      // 组装数据格式
      const schema: LanhaiDataSchema = {
        dataSource: item.data_source,
        pk: item.pk,
        tableDesc: item.table_desc,
        tableName: item.table_name,
        data: item.data,
      };
      let content = JSONBig.stringify(schema, null, 2);
      console.log(`导出表内容 - ${item.table} - ${content}`);
      if (encrypt) {
        // 加密
        const { value } = await invoke<{ value: string }>('encrypt_content', {
          content,
        });
        console.log(`加密 - ${item.table} - ${table_file}`);
        content = value;
      }

      await writeFile(table_file, content);

      console.log(`导出表 - ${item.table} - ${table_file}`);
    }));

    // 其它附属文件
    const data_source = encrypt
      ? (await invoke<{ value: string }>('encrypt_content', {
          content: JSONBig.stringify(this.other.dataSource),
        })).value
      : JSONBig.stringify(this.other.dataSource);
    await writeFile(await join(this.root, 'dataSource.json'), data_source);
    const readme = {
      ...this.other.Readme,
      fileCode: MD5(this.fileName + (await localCnnfig()).salt),
      fileName: this.fileName,
    };
    console.log(`生成 MD5 ${this.fileName} ${MD5(this.fileName + (await localCnnfig()).salt)}`, readme);
    const readme_data = encrypt
      ? (await invoke<{ value: string }>('encrypt_content', {
          content: JSONBig.stringify(readme),
        })).value
      : JSONBig.stringify(readme);
    await writeFile(await join(this.root, 'Readme.json'), readme_data);
    console.log('生成其它附属文件 - dataSource.json - Readme.json');

    // 处理需要导出的文件
    const { sys_file } = this.results;
    await Promise.all((sys_file.data as LanhaiFile[]).map(async (file) => {
      const file_name = file.NEW_FNAME_;
      const source_file = await join(await filesPath(), file_name);
      const target_file = await join(files_path, file_name);
      // 复制文件到导出文件夹
      console.log(`复制文件 - ${source_file} 到 - ${target_file} 开始`);
      await copyFile(source_file, target_file);
      console.log(`复制文件 - ${source_file} 到 - ${target_file} 完成`);
    }));

    // 压缩
    console.log(`压缩 - ${this.tmp} - ${this.file} 开始`);
    await invoke('zip', {
      sourcePath: this.tmp,
      targetFile: this.file,
    });
    console.log(`压缩 - ${this.tmp} - ${this.file} 完成`);

    if (encrypt) {
      // 加密压缩包
      console.log(`加密压缩包 - ${this.file} - ${this.data} 开始`);
      await invoke('encrypt_file', {
        sourceFile: this.file,
        targetFile: this.data,
      });
      console.log(`加密压缩包 - ${this.file} - ${this.data} 结束`);
    }
    else {
      await renameFile(this.file, this.data);
      console.log(`重命名 - ${this.file} - ${this.data} 完成`);
    }

    // 移除临时目录
    console.log(`移除临时目录 - ${this.tmp}`);
    await removeDir(this.tmp, {
      recursive: true,
    });
    // 移除临时文件
    console.log(`移除临时文件 - ${this.file}`);
    await removeFile(this.file);

    return this;
  }

  // 更新记录
  async update() {
    // 准备需要更新的主表数据
    const records = Object.values(this.results).filter(item => item.main && !!item.data.length);

    if (!records.length)
      return false;

    // 单独处理各模块
    const results: ExportPayload = {
      programme: [],
      book: [],
      report: null,
      rectification: null,
      meeting: [],
      record: [],
      action: {
        name: this.data,
        module: '',
        types: this.type.toString(),
      },
    };

    const modules: string[] = [];
    records.forEach((item) => {
      !!item.table_desc && modules.push(item.table_desc);
      item.data.forEach((_item: any) => {
        const module = item.module as keyof ExportPayload;

        // record
        if (module === FILL_TABLE_COLLECTION.RECORD) {
          (results[module] as ValueOf<Pick<ExportPayload, 'record'>>)?.push({
            uuid: _item.ID_,
            relation_uuid: _item.F_RELID_,
            module: _item.F_TYPE_,
            content: JSONBig.stringify(_item),
            approval_at: '',
            sended_at: _item.F_FSSJ_,
          });

          return;
        }

        // 获取 state
        const node = (this.results[TABLE_COLLECTION.SP_NODE].data as w_sp_node[]).find(_node => _item.ID_ === _node.F_REL_ID_ && item.module === NODE_KEY[_node.F_TYPE_ as keyof typeof NODE_KEY]);

        // report rectification
        if ([FILL_TABLE_COLLECTION.REPORT, FILL_TABLE_COLLECTION.RECTIFICATION].includes(item.module as ValueOf<typeof FILL_TABLE_COLLECTION>)) {
          // results.book = JSONBig.stringify(item.data[0]);
          (results[module] as ValueOf<Omit<ExportPayload, 'programme' | 'book' | 'meeting'>>) = JSONBig.stringify({
            ...(this.config[item.module].value as any),
            status: STATUS_VALUE.NORMAL,
            state: node?.F_STATE_ as string,
          });
          return;
        }

        // programme book meeting
        (results[module] as ValueOf<Omit<ExportPayload, 'report' | 'rectification' | 'record' | 'action'>>)?.push({
          uuid: _item.ID_,
          state: node?.F_STATE_ as string,
        });
      });
    });

    results.action!.module = modules.join();

    // 重命名导出包
    const module_keys = modules.map(item => NODE_TYPE_ENUM[item as keyof typeof NODE_TYPE_ENUM]);
    const base_name = await basename(this.data);
    const name = base_name.substring(0, base_name.lastIndexOf('.'));
    console.log(name);
    const new_name = await join(await dataPath(), `${name}&&${module_keys.join('')}.chrtc`);
    await renameFile(this.data, new_name);
    this.data = new_name;

    results.action!.name = new_name;

    // 重命名输出文件
    console.log(results);

    await queryClient.fetchQuery({
      queryKey: ['handle.export'],
      queryFn: () => client.mutation(['handle.export', { arg: results }]),
    });

    await queryClient.refetchQueries({
      stale: true,
    });

    return this;
  }
}
